use crate::types::{Type,Wrapped,Row,Separated,Constraint};


pub fn everything_on_types<R,A:Clone>(op:fn(R,R)->R,k:fn(&Type<A>) -> R,ty:&Type<A>) -> R {
    go_ty(op,k,ty)
}


fn go_ty<R,A:Clone>(op:fn(R,R)->R,k:fn(&Type<A>) -> R,ty:&Type<A>) -> R {
    match ty {
        Type::TypeVar(_,_) => k(ty),
        Type::TypeConstructor(_,_) => k(ty),
        Type::TypeWildcard(_,_) => k(ty),
        Type::TypeHole(_,_) => k(ty),
        Type::TypeString(_,_,_) => k(ty),
        Type::TypeRow(_,Wrapped{value,..}) => go_row(op, k, value, ty),
        Type::TypeRecord (_,Wrapped {value,..}) => go_row(op, k, value, ty),
        Type::TypeForall(_,_,_,_,ty2) => op(k(ty),go_ty(op, k, ty2)),
        Type::TypeKinded(_,ty2,_,ty3) => op(k(ty),op(go_ty(op, k,ty2),go_ty(op, k,ty3))),
        Type::TypeApp(_,ty2,ty3) => op(k(ty),op(go_ty(op, k,ty2),go_ty(op, k,ty3))),
        Type::TypeOp(_,ty2,_,ty3) => op(k(ty),op(go_ty(op, k,ty2),go_ty(op, k,ty3))),
        Type::TypeOpName(_,_) => k(ty),
        Type::TypeArr(_,ty2,_,ty3) => op(k(ty),op(go_ty(op, k,ty2),go_ty(op, k,ty3))),
        Type::TypeArrName(_,_) => k(ty),
        Type::TypeConstrained(_,constraint,_,ty3) => {
            
            let mut ref_const:&Constraint<A> = &*constraint;
            loop {
                match ref_const {
                    Constraint::Constraint(_,_,arr) => {
                        if arr.len() == 0 {
                            return op(k(ty),go_ty(op, k,ty3)); 
                        } else {
                            let mut k0 = k(ty);
                            for ty in arr {
                                k0 = op(k0,k(ty)) 
                             }
                             return k0;
                        }
                    },
                    Constraint::ConstraintParens(_,wrap) => { ref_const = &wrap.value }
                };
            }
        },
        
        Type::TypeParens(_,Wrapped {value,..}) => op(k(ty),go_ty(op, k,value)),
        Type::TypeUnaryRow(_,_,ty2) => op(k(ty),go_ty(op, k,ty2)),
    }
}

fn go_row<A:Clone,R>(op:fn(R,R)->R,k:fn(&Type<A>) -> R,row:&Row<A>,ty:&Type<A>) -> R {
    match row {
        Row {labels:None,tail:None} => k(ty),
        Row {labels:None,tail:Some((_,ty2)) } => op(k(ty),go_ty(op,k,&ty2)),
        Row {labels:Some(lbls),tail:None } => op(k(ty),everything_on_separated(op,|a| go_ty(op, k, &a.value) , &lbls)),
        Row {labels:Some(lbls),tail:Some((_,ty2)) } => {
            let k0 = k(ty);
            let k1 = everything_on_separated(op,|a| go_ty(op, k, &a.value),&lbls);
            let k2 = go_ty(op, k,&ty2);
            op(op(k0,k1),k2)
        }
    }
}

fn everything_on_separated<R,A>(op:fn(R,R)->R,k:impl Fn(&A) -> R,sep:&Separated<A>) -> R {
    let len = sep.tail.len();
    let mut arr_idx = len;
    let mut ret = k(&sep.head);
    loop {
        if arr_idx <= 0 {
            let ka = k(&sep.tail[0].1);
            ret = op(ret,ka);
            return ret;
        }
        let ka = k(&sep.tail[arr_idx].1);
        ret = op(ret,ka);
        arr_idx -= 1
    }
}

